package com.ccteam.admin.core

import android.content.Context
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import androidx.datastore.dataStore
import com.ccteam.admin.datastore.protobuf.AdminInfoProtos
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject
import javax.inject.Singleton

/**
 * @author Xiaoc
 * @since 2021/4/7
 *
 * 管理端核心基础
 * 目前内部包含了登录状态和管理员基本内容的封装
 * 并提供了登录退出功能的操作
 *
 * 登录状态等内容均使用Flow来进行流通知
 */
@Singleton
class AdminCore @Inject constructor(
    @ApplicationContext private val context: Context
){

    // 协程组件
    private val serviceJob = SupervisorJob()
    private val serviceScope = CoroutineScope(Dispatchers.Main + serviceJob)

    /**
     * 是否登录成功，false 处于未登录 true 为已登录
     */
    private val _isLogin = MutableSharedFlow<Boolean>(replay = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST)
    val isLogin get() = _isLogin.asSharedFlow()

    private val _userInfo = MutableStateFlow<AdminInfoProtos.AdminInfo>(
        AdminInfoProtos.AdminInfo.getDefaultInstance()
    )
    val userInfo get() = _userInfo.asStateFlow()

    init {
        observeLoginState()
    }

    /**
     * 登录账号，会更新当前在线状态
     * @param id 用户ID
     * @param userToken 用户Token
     */
    fun login(id: String,userToken: String,adminName: String){
        serviceScope.launch {
            context.userInfoDataStore.updateData {
                it.toBuilder().setAdminId(id)
                    .setAdminToken(userToken)
                    .setAdminName(adminName).build()
            }
        }
    }

    /**
     * 登出账号
     */
    fun logout(){
        _isLogin.tryEmit(false)
        serviceScope.launch {
            context.userInfoDataStore.updateData { userInfo ->
                userInfo.defaultInstanceForType
            }
        }
    }

    /**
     * 刷新当前登录状态
     * 当进行了登录操作，应该调用此方法进行更新，因为登录状态已经发生变化
     */
    private fun observeLoginState(){
        serviceScope.launch {
            context.userInfoDataStore.data.collectLatest {
                _userInfo.value = it
                _isLogin.tryEmit(!it.adminId.isNullOrEmpty() && !it.adminToken.isNullOrEmpty())
            }
        }
    }
}

private const val adminInfoDataStoreFileName = "adminInfo.pb"

/**
 * 当前管理员信息暂存的DataStore
 */
private val Context.userInfoDataStore by dataStore(
    fileName = adminInfoDataStoreFileName,
    serializer = AdminInfoSerializer
)

/**
 * [AdminInfoProtos.AdminInfo] 的序列化器
 * 为什么要写这个是因为Java自带序列化用了耗费资源性能的反射
 * 而Android利用该序列化器将其显式调用防止耗费多余性能
 */
@Suppress("BlockingMethodInNonBlockingContext")
private object AdminInfoSerializer: Serializer<AdminInfoProtos.AdminInfo> {
    override val defaultValue: AdminInfoProtos.AdminInfo
        get() = AdminInfoProtos.AdminInfo.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): AdminInfoProtos.AdminInfo {
        return withContext(Dispatchers.IO){
            try {
                // 是编译器自动生成的，用于读取并解析 input 的消息
                return@withContext AdminInfoProtos.AdminInfo.parseFrom(input)
            } catch (e: Exception){
                throw CorruptionException("无法读取Proto内容 ${e.message}")
            }
        }

    }

    override suspend fun writeTo(t: AdminInfoProtos.AdminInfo, output: OutputStream) {
        // t.writeTo(output) 是编译器自动生成的，用于写入序列化消息
        withContext(Dispatchers.IO){
            t.writeTo(output)
        }
    }

}
